<template>
  <div class="audition249">
    <p>推荐阅读：<a href="https://juejin.cn/post/7062132944059498510" target="_blank">手写Vue响应式原理</a></p>
    <p>推荐视频：<a href="https://www.bilibili.com/video/BV1G54y1s7xV?p=1" target="_blank">vue响应式原理视频</a></p>
    <p>完整代码：</p>
    <v-md-preview :text="text1" />
  </div>
</template>

<script>
const prefix = '``` js'
const suffix = '```'
export default {
  name: 'Audition249',
  data() {
    return {
      text1: `${prefix}
      const arrayMethods = Object.create(Array.prototype)
      const methodsNeedChange = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
      for (let i = 0; i < methodsNeedChange.length; i++) {
        const original = arrayMethods[methodsNeedChange[i]]
        def(arrayMethods, methodsNeedChange[i], function () {
          let inserted = []
          let args = [...arguments]
          switch (methodsNeedChange[i]) {
            case 'push':
            case 'unshift':
              inserted = args
              break
            case ' ':
              inserted = args.slice(2)
          }
          inserted.length && observeArray(inserted)
          original.apply(this, arguments)
        }, false)
      }
      function defineReactive(obj, key, val) {
        let dep = new Dep()
        let childOb
        if(arguments.length === 2) {
          val = obj[key]
          childOb = observe(val)
        }
        Object.defineProperty(obj, key, {
          enumerable: true,
          configurable: true,
          get() {
            if(Dep.target) {
              dep.depend()
              if(childOb) {
                childOb.dep.depend()
              }
            }
            return val
          },
          set(newVal) {
            val = newVal
            childOb = observe(val)
            dep.notify()
          }
        })
      }
      function def(obj, key, value, enumerable) {
        Object.defineProperty(obj, key, {
          value,
          enumerable
        })
      }
      class Observer {
        constructor(obj) {
          this.dep = new Dep()
          def(obj, '__ob__', this, false)
          if(Array.isArray(obj)) {
            observeArray(obj)
            Object.setPrototypeOf(obj, arrayMethods)
          } else {
            this.walk(obj)
          }
        }
        walk(obj) {
          let keys = Object.keys(obj)
          for(let i = 0; i < keys.length; i++) {
            defineReactive(obj, keys[i])
          }
        }
      }
      // 监听入口
      function observe(value) {
        if (typeof value !== 'object') return
        let ob
        if(value.hasOwnProperty('__ob__') && value.__ob__ instanceof Observer) {
          ob = value.__ob__
        } else {
          ob = new Observer(value)
        }
        return ob
      }
      function observeArray(arr) {
        for(let i = 0; i < arr.length; i++) {
          observe(arr[i])
        }
      }
      class Dep {
        constructor() {
          this.subs = []
        }
        depend() {
          if(Dep.target) {
            this.subs.push(Dep.target)
          }
        }
        notify() {
          const subs = this.subs.slice()
          for(let i = 0; i < subs.length; i++){
            subs[i].update()
          }
        }
      }
      class Watcher {
        constructor(target, expression, cb) {
          this.target = target
          this.getter = parsePath(expression)
          this.cb = cb
          this.value = this.get()
        }
        update() {
          const oldValue = this.value
          const newValue = this.get()
          this.cb(newValue, oldValue)
        }
        get() {
          Dep.target = this
          const obj = this.target
          let value
          try {
            value = this.getter(obj)
          } finally {
            Dep.target = null
          }
          this.value = value
          return value
        }
      }
      function parsePath(str) {
        let segments = str.split('.')
        return obj => {
          for(let i = 0; i < segments.length; i++) {
            if(!obj) return
            obj = obj[segments[i]]
          }
          return obj
        }
      }
      let obj = {
        name: {
          name1: {
            name2: 'dilireba'
          }
        }
      }
      observe(obj)
      new Watcher(obj, 'name.name1.name2', (val, oldValue) => {
        console.log('@@: ', val, oldValue)
      })
      \n${suffix}`,
    }
  }
}
</script>

<style>

</style>